package edu.pitt.cs.cs2510.project3.model;

import java.nio.BufferOverflowException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;

import edu.pitt.cs.cs2510.project3.configuration.InputParameters;

public class Client {
	public int id;
	private InputParameters simulationParams;
	private Map<Request, Queue<FileBlock>> buffer;
	List<Request> completedRequests;

	public Client(int id, InputParameters simulationParams) {
		super();
		this.id = id;
		this.simulationParams = simulationParams;
		this.buffer = new HashMap<Request, Queue<FileBlock>>();
		this.completedRequests = new LinkedList<Request>();
	}
	
	public void addRequest(Request request) {
		this.buffer.put(request, new LinkedList<FileBlock>());
	}
	
	public synchronized int getBufferSize(Request request) {
		if (!this.buffer.containsKey(request))
		{
			return this.simulationParams.getBufferSize(this.id);
		}
		return this.simulationParams.getBufferSize(this.id) - this.buffer.get(request).size();
	}
	
	public void addFileBlock(Request request, FileBlock fileBlock)
	{
		if (!this.buffer.containsKey(request))
		{
			throw new IllegalArgumentException("Not my file block");
		}
		
		if (this.buffer.get(request).size() >= this.simulationParams.getBufferSize(this.id))
		{
			throw new BufferOverflowException();
		}
		this.buffer.get(request).add(fileBlock);
	}
	
	public void tick() {
		Iterator<Request> iterator = this.buffer.keySet().iterator();
		while(iterator.hasNext()) {
			Request request = iterator.next();
			for (FileBlock fileBlock : this.buffer.get(request)) {
				if (fileBlock.timeInTransit > 0) {
					fileBlock.timeInTransit--;
				}
			}
			
			if (this.buffer.get(request).isEmpty())
			{
				request.lag++;
				continue;
			}
			
			
			if (this.buffer.get(request).peek().timeInTransit <= 0 && --this.buffer.get(request).peek().lifeTime <= 0)
			{
				FileBlock fileBlock = this.buffer.get(request).poll();
				
				if (fileBlock.blockId >= request.size)
				{
					this.completedRequests.add(request);
					iterator.remove();
				}
			}
		}
	}

	public Server getClosestServerWithFile(Integer fileId, Collection<Server> servers) {
		Server closest = null;
		for (Server server : servers)
		{
			if (server.hasFileBlock(fileId, 0))
			{
				if (closest == null)
				{
					closest = server;
				}
				else if (this.simulationParams.getDistance(this.id, server.id) < this.simulationParams.getDistance(this.id, closest.id))
				{
					closest = server;
				}
			}
		}

		return closest;
	}

	@Override
	public String toString() {
		return "[Client id=" + id + "]";
	}

	public boolean isDone() {
		return this.buffer.isEmpty();
	}
}